#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import json
import locale
import os
import platform
import re
import shlex
import shutil
import sqlite3
import stat
import subprocess
import time
from contextlib import contextmanager
from functools import wraps
from stat import S_ISDIR, S_ISLNK, S_ISREG
from threading import Thread

import grp
import pwd

from common.common import check_command_injection_ex
from common.const import ExecuteResultEnum, RpcToolInterface, EnumPathType, PathConstant, SysData
from common.const import ParamConstant, RpcParamKey, CMDResult
from common.env_common import get_install_head_path
from common.exception.common_exception import ErrCodeException
from common.file_common import exec_lchown_dir_recursively
from common.job_const import ParamKeyConst
from common.util.cmd_utils import cmd_format
from common.util.exec_utils import exec_mkdir_cmd, exec_mount_cmd, exec_umount_cmd, exec_mount_cmd_with_aix, \
    exec_cp_cmd, ExecFuncParam, su_exec_cmd_list, su_exec_rm_cmd
from common.util.validators import ValidatorEnum
from informix import LOGGER
from informix.comm.common_models import ActionResult, CopyDataModel
from informix.comm.const import RpcParamKey, JsonConstant, FileConstant, LastCopyType, InformixBaseCommand, \
    InformixCustomParmConstant, InformixInfo, PathInfo, PERMISSION_600, PERMISSION_700, InformixPath, ErrorCode, \
    GbaseInfo, GbaseCommand, GbaseExportCmdConstant, InformixExportCmdConstant, MountType


def modify_path_permissions(path: str):
    """
    集群环境，恢复时clone文件系统需要多个主机同时读取，同时需要在文件夹生成新文件，需要文件夹对other支持读写权限
    """
    modes = stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO | stat.S_ISVTX
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
    if not os.path.exists(path):
        with os.fdopen(os.open(path, flags, modes), 'w') as file:
            LOGGER.info(f"success touch file")
    try:
        os.chmod(path, modes)
    except Exception as exception:
        LOGGER.error(f"exception: {exception}")
    return True


def informix_open_log_backup(db_user=InformixInfo.INFORMIX_USER):
    """
    只用于执行开启日志备份开关命令
    :return:
    """
    cmd = f'su - {db_user} -c "onbar -b -l -C 1>/dev/null 2>&1 &"'
    ret, out, err = execute_cmd(cmd)
    if ret != CMDResult.SUCCESS:
        LOGGER.warning("Open local log backup fail")
    return CMDResult.SUCCESS.value, '', ''


def check_path_in_white_list(path_: str):
    """
    校验真实路径、特殊字符、白名单
    :param path_: 校验路径
    :return: 校验结果和真是路径
    """
    if path_.endswith("/"):
        path_ = path_[:-1]
    try:
        real_path = os.path.realpath(path_)
    except Exception as e_info:
        LOGGER.exception(e_info)
        LOGGER.error("Path verification error.")
        return False, ''
    if real_path != path_:
        LOGGER.error(f"Path: {path_} is not real Path")
        return False, ''
    if check_command_injection(real_path):
        LOGGER.error("Invalid path.")
        return False, ''
    for path in FileConstant.WHITE_FILE_LIST:
        if real_path.find(path) == 0:
            return True, real_path
    LOGGER.error("The path is not in the trustlist.")
    return False, ''


def record_result(ignore_normal_record=False, ignore_exception_record=False):
    """
    输出执行结果装饰器，任务上报输出结果统一入口
    :param ignore_normal_record:（执行结果正常）是否忽略上报
    :param ignore_exception_record: （执行结果异常）是否忽略上报
    """

    def decorate(func):
        @wraps(func)
        def inner(obj, *args):
            """
            :param obj: restore_service实例
            """
            pid = obj.pid
            excepted = False
            code = ExecuteResultEnum.SUCCESS
            try:
                func(obj, *args)
            except ErrCodeException as ex:
                excepted = True
                err_code, err_msg = ex.error_code, ex.error_message
                LOGGER.exception(f"Execute task failed, function name: %s, error code: %s, "
                                 f"error message: %s.", func.__name__, err_code, err_msg)
            except Exception as ex:
                excepted = True
                LOGGER.exception(f"Execute task failed. Function Name: %s.", func.__name__)
                err_code = ExecuteResultEnum.INTERNAL_ERROR
                err_msg = f"Execute task failed. Error Message: {str(ex)}."
            else:
                err_code = ExecuteResultEnum.SUCCESS
                err_msg = ""

            if ignore_normal_record and excepted == ignore_exception_record:
                return ''
            if excepted:
                code = ExecuteResultEnum.INTERNAL_ERROR
            output = obj.result
            if output is None:
                output = ActionResult(code=code)
            if isinstance(output, ActionResult):
                output.code = code.value
                output.body_err = err_code
                output.message = err_msg
                output = output.dict(by_alias=True)
            LOGGER.info(f"Task result:%s", output)
            output_result_file(pid, output)
            return output

        return inner

    return decorate


def report_job_details(job_id: str, sub_job_details: dict):
    try:
        result_info = exec_rc_tool_cmd(job_id, RpcParamKey.REPORT_JOB_DETAILS, sub_job_details)
    except Exception as err:
        LOGGER.error(f"Invoke rpc_tool interface exception, err: %s.", err)
        return False
    if not result_info:
        return False
    ret_code = result_info.get("code", -1)
    if ret_code != int(CMDResult.SUCCESS):
        LOGGER.error(f"Invoke rpc_tool interface failed, result code: %s.", ret_code)
        return False
    return True


@contextmanager
def report_progress(report_step, *report_args):
    rp_thread = Thread(target=report_step, args=report_args)
    rp_thread.setDaemon(True)
    rp_thread.start()
    yield rp_thread
    rp_thread.join()


def job_report(job_id, sub_job_details):
    return exec_rc_tool_cmd(job_id, RpcToolInterface.REPORT_JOB_DETAIL, sub_job_details)


def exec_rc_tool_cmd(unique_id, interface_name, param_dict):
    """
    执行rc_tool命令
    @@param cmd: 需要执行的命令
    @@param in_param: 需要写入输入文件的命令参数
    @@param unique_id: 输入输出文件唯一标识
    @@return result:bool 命令执行结果
    @@return output:string 命令输出
    """

    def clear_file(path):
        if not os.path.exists(path):
            return
        if not check_path_legal(path, FileConstant.INFORMIX_FILESYSTEM_MOUNT_PATH):
            return
        if os.path.isfile(path):
            su_exec_rm_cmd(path)

    input_file_path = os.path.join(ParamConstant.PARAM_FILE_PATH, ParamConstant.INPUT_FILE_PREFFIX + unique_id)
    output_file_path = os.path.join(ParamConstant.RESULT_PATH, ParamConstant.OUTPUT_FILE_PREFFIX + unique_id)
    ret, real_path = check_path_in_white_list(output_file_path)
    if not ret:
        raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
    output_execution_result_ex(input_file_path, param_dict)
    cmd = f"{os.path.join(ParamConstant.BIN_PATH, 'rpctool.sh')} {interface_name} {input_file_path} {output_file_path}"
    try:
        ret, std_out, std_err = execute_cmd(cmd)
    except Exception as err:
        raise err
    finally:
        # 执行命令后删除输入文件
        clear_file(input_file_path)

    if ret != CMDResult.SUCCESS:
        LOGGER.error(f"ret: {ret}, std_out: {std_out}, std_err: {std_err}.")
        return {}

    # 读取文件成功后删除文件
    try:
        with open(output_file_path, "r", encoding='utf-8') as tmp:
            result = json.load(tmp)
    except Exception as err:
        raise err
    finally:
        clear_file(output_file_path)

    return result


def get_local_uuid():
    if not os.path.exists(JsonConstant.HOSTSN_FILE_PATH):
        return ''
    with open(JsonConstant.HOSTSN_FILE_PATH, 'r') as f_object:
        hostsn = f_object.read()
    hostsn = hostsn.strip("\n")
    return hostsn


def get_last_copy_info(backup_type, job_id, sub_job_id, application):
    """
    查询上一次的copy_id
    :param application:
    :param sub_job_id:
    :param job_id:
    :param backup_type:
    class BackupTypeEnum(int, Enum):
    # 备份类型
    FULL_BACKUP = 1
    INCRE_BACKUP = 2
    DIFF_BACKUP = 3
    LOG_BACKUP = 4
    """
    last_copy_type = LastCopyType.last_copy_type_dict.get(backup_type, [])
    if not last_copy_type:
        return {}
    input_param = {
        RpcParamKey.APPLICATION: application,
        RpcParamKey.TYPES: last_copy_type,
        RpcParamKey.COPY_ID: "",
        ParamKeyConst.JOB_ID: job_id
    }
    return exec_rc_tool_cmd(f"{job_id}{sub_job_id}", RpcParamKey.QUERY_PREVIOUS_CPOY, input_param)


def exec_echo_env_var(env_var):
    echo_cmd = cmd_format(InformixBaseCommand.ECHO_TARGET_ENV, env_var)
    return_code, out_info, err_info = execute_cmd(echo_cmd)
    ret = (return_code == CMDResult.SUCCESS.value)
    res_cont = out_info if ret else err_info
    return ret, res_cont


def get_uid_gid(user_name):
    return pwd.getpwnam(user_name).pw_uid, pwd.getpwnam(user_name).pw_gid


def set_user(path, user):
    ret, real_path = check_path_in_white_list(path)
    if not ret:
        return False
    try:
        uid, gid = get_uid_gid(user)
    except Exception as e_info:
        LOGGER.exception(e_info)
        return False
    os.lchown(real_path, int(uid), int(gid))
    return True


def set_permisson(path, permisson):
    ret, real_path = check_path_in_white_list(path)
    if not ret:
        raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
    os.chmod(real_path, permisson)
    return True


def mkdir_set_permissions(path_, username_, mount_type=""):
    """
    设置目录及其父目录所属用户
    :param path_: 目录路径
    :param username_: 用户名
    :return:
    """
    if not os.path.exists(path_):
        if mount_type == MountType.FUSE:
            os.makedirs(path_)
        else:
            ret, _ = check_path_in_white_list(path_)
            if not ret:
                return False
            try:
                exec_mkdir_cmd(path_)
            except Exception as e_obj:
                LOGGER.exception(e_obj)
                LOGGER.info(f"Create path fail,%s", path_)
                return False
    # 获取uid和gid
    try:
        uid, gid = get_uid_gid(username_)
    except Exception as e_info:
        LOGGER.exception(e_info)
        return False
    os.lchown(path_, int(uid), int(gid))
    p_path_info = path_.split('/')[0:-1]
    p_path = '/'.join(p_path_info)
    os.lchown(p_path, int(uid), int(gid))
    return True


def check_command_injection(param):
    """
    检测是否含有特殊字符防止命令注入
    :param : shell 执行参数
    :return: bool
    """

    expression = "[|;&$><`!+\n]"
    if re.search(expression, param):
        return True
    return False


def invoke_rpc_tool_interface(unique_id: str, interface_name: str, input_dict: dict):
    # 因为是固定路径所以不用做校验
    def clear_file(path):
        if os.path.isfile(path):
            su_exec_rm_cmd(path)

    input_file_path = os.path.join(RpcParamKey.PARAM_FILE_PATH, RpcParamKey.INPUT_FILE_PREFFIX + unique_id)
    output_file_path = os.path.join(RpcParamKey.RESULT_PATH, RpcParamKey.OUTPUT_FILE_PREFFIX + unique_id)
    output_execution_result(input_file_path, input_dict)
    if check_command_injection(interface_name):
        err_info = f"Interface name err, interface_name: {interface_name}."
        raise Exception(err_info)
    if check_command_injection(unique_id):
        err_info = f"Unique id err, unique_id: {unique_id}."
        raise Exception(err_info)
    cmd = f"sh {RpcParamKey.RPC_TOOL} {interface_name} {input_file_path} {output_file_path}"
    # 执行命令后不论结果都需要删除输入文件
    try:
        ret, std_out, std_err = execute_cmd(cmd)
    except Exception as err:
        raise err
    finally:
        clear_file(input_file_path)

    if ret != CMDResult.SUCCESS.value:
        err_info = f"Invoke rpc_tool script failed, std_err: {std_err}."
        raise Exception(err_info)

    # 不管读取文件是否成功都需要删除文件
    try:
        with open(output_file_path, "r", encoding='utf-8') as tmp:
            result = json.load(tmp)
    except Exception as err:
        raise err
    finally:
        clear_file(output_file_path)
    return result


def set_xbsa_user(db_user=InformixInfo.INFORMIX_USER):
    ret = exec_lchown_dir_recursively(PathConstant.XBSA_PATH, db_user, db_user)
    if not ret:
        return False
    return True


def remove_dir(dir_path):
    if not os.path.exists(dir_path):
        LOGGER.warn(f"Path: {dir_path} not exist, no need remove.")
        return True
    ret, _ = check_path_in_white_list(dir_path)
    if not ret:
        raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
    try:
        shutil.rmtree(dir_path)
    except Exception as e_obj:
        LOGGER.exception(e_obj)
        return False
    return True


def exec_cmd_with_informix_env_according_to_system(env_dict, cmd_info, db_user=InformixInfo.INFORMIX_USER):
    if platform.system().lower() == "aix":
        LOGGER.info("Exec cmd in aix system.")
        return exec_cmd_with_informix_env_old(env_dict, cmd_info, db_user)
    else:
        return exec_cmd_with_informix_env(env_dict, cmd_info, db_user)


def exec_cmd_with_informix_env(env_dict, cmd_info, db_user=InformixInfo.INFORMIX_USER):
    try:
        ret, out, err = exec_cmd_with_informix_env_new(env_dict, cmd_info, db_user)
    except Exception as ex:
        LOGGER.error(f"exec cmd with informix env new failed, error: {ex}")
        return exec_cmd_with_informix_env_old(env_dict, cmd_info, db_user)

    if ret == CMDResult.SUCCESS:
        return ret, out, err
    else:
        LOGGER.error("exec cmd with informix env new failed")
        return exec_cmd_with_informix_env_old(env_dict, cmd_info, db_user)


def iif_exec_cmd_with_informix_env(env_dict, cmd_info, db_user=InformixInfo.INFORMIX_USER):
    try:
        ret, out, err = iif_exec_cmd_with_informix_env_new(env_dict, cmd_info, db_user)
    except Exception as ex:
        LOGGER.error(f"iff exec cmd with informix env failed: error:{ex}")
        return iif_exec_cmd_with_informix_env_old(env_dict, cmd_info, db_user)

    if ret == CMDResult.SUCCESS:
        return ret, out, err
    else:
        LOGGER.error("iff exec cmd with informix env failed")
        return iif_exec_cmd_with_informix_env_old(env_dict, cmd_info, db_user)


def get_basic_conf(env_dict):
    instance_name = env_dict.get(InformixCustomParmConstant.INSTANCE_NAME, "")
    sqlhosts_path = env_dict.get(InformixCustomParmConstant.SQLHOSTS_PATH, "")
    onconfig_name = env_dict.get(InformixCustomParmConstant.CONFIG_NAME, "")
    return instance_name, sqlhosts_path, onconfig_name


def get_server_and_hosts(db_user):
    server_name = GbaseExportCmdConstant.GBASE_SERVER if db_user == GbaseInfo.GBASE_USER \
        else InformixExportCmdConstant.INFORMIX_SERVER
    sql_hosts_name = GbaseExportCmdConstant.GBASE_SQLHOSTS if db_user == GbaseInfo.GBASE_USER \
        else InformixExportCmdConstant.INFORMIX_SQLHOSTS
    return server_name, sql_hosts_name


def exec_cmd_with_informix_env_new(env_dict, cmd_info, db_user=InformixInfo.INFORMIX_USER):
    instance_name, sqlhosts_path, onconfig_name = get_basic_conf(env_dict)
    server_name, sql_hosts_name = get_server_and_hosts(db_user)
    if not all([instance_name, sqlhosts_path, onconfig_name]):
        return CMDResult.FAILED.value, '', ''
    param = ExecFuncParam(os_user=db_user, cmd_list=[
        f"export {server_name}={{instance_name}} ONCONFIG={{onconfig_name}}"
        f" {sql_hosts_name}={{sqlhosts_path}}; {{cmd_info}}"],
                          fmt_params_list=[[("instance_name", instance_name, ValidatorEnum.CHAR_CHK_COMMON),
                                            ("onconfig_name", onconfig_name, ValidatorEnum.CHAR_CHK_COMMON),
                                            ("sqlhosts_path", sqlhosts_path, ValidatorEnum.PATH_CHK_FILE),
                                            ("cmd_info", cmd_info, ValidatorEnum.CHAR_CHK_INCLUDE_ARROWHEAD)]],
                          shell_file="/bin/sh", chk_exe_owner=False)
    return_code, out = su_exec_cmd_list(param)
    return return_code, out, ""


def iif_exec_cmd_with_informix_env_new(env_dict, cmd_info, db_user=InformixInfo.INFORMIX_USER):
    instance_name, sqlhosts_path, onconfig_name = get_basic_conf(env_dict)
    server_name, sql_hosts_name = get_server_and_hosts(db_user)
    if not all([instance_name, sqlhosts_path, onconfig_name]):
        return CMDResult.FAILED.value, '', ''
    param = ExecFuncParam(os_user=db_user, cmd_list=[
        f"export {server_name}={{instance_name}} ONCONFIG={{onconfig_name}}"
        f" {sql_hosts_name}={{sqlhosts_path}}; {{cmd_info}}"],
                          fmt_params_list=[[("instance_name", instance_name, ValidatorEnum.CHAR_CHK_COMMON),
                                            ("onconfig_name", onconfig_name, ValidatorEnum.CHAR_CHK_COMMON),
                                            ("sqlhosts_path", sqlhosts_path, ValidatorEnum.PATH_CHK_FILE),
                                            ("cmd_info", cmd_info, ValidatorEnum.CHAR_CHK_COMMON)]],
                          shell_file="/bin/sh", chk_exe_owner=False)
    return_code, out = su_exec_cmd_list(param)
    return return_code, out, ""


def exec_cmd_with_informix_env_old(env_dict, cmd_info, db_user=InformixInfo.INFORMIX_USER):
    instance_name, sqlhosts_path, onconfig_name = get_basic_conf(env_dict)
    if not all([instance_name, sqlhosts_path, onconfig_name]):
        return CMDResult.FAILED, '', ''
    env_cmd = GbaseCommand.ENV_CMD if db_user == GbaseInfo.GBASE_USER else InformixBaseCommand.ENV_CMD
    exec_cmd_with_quote = GbaseCommand.EXEC_WITH_GBASEDBT_WITH_QUOTATION \
        if db_user == GbaseInfo.GBASE_USER else InformixBaseCommand.EXEC_WITH_INFORMIX_WITH_QUOTATION
    cmd = env_cmd.format(instance_name, onconfig_name, sqlhosts_path, cmd_info)
    cmd = exec_cmd_with_quote.format(cmd)
    LOGGER.info(f"cmd: {cmd}")
    return execute_cmd(cmd)


def iif_exec_cmd_with_informix_env_old(env_dict, cmd_info, db_user=InformixInfo.INFORMIX_USER):
    instance_name, sqlhosts_path, onconfig_name = get_basic_conf(env_dict)
    if not all([instance_name, sqlhosts_path, onconfig_name]):
        return CMDResult.FAILED.value, '', ''
    env_cmd = GbaseCommand.ENV_CMD if db_user == GbaseInfo.GBASE_USER else InformixBaseCommand.ENV_CMD
    exec_cmd_with_quote = GbaseCommand.EXEC_WITH_GBASEDBT_WITH_QUOTATION \
        if db_user == GbaseInfo.GBASE_USER else InformixBaseCommand.EXEC_WITH_INFORMIX_WITH_QUOTATION
    cmd = env_cmd.format(instance_name, onconfig_name, sqlhosts_path, cmd_info)
    cmd = exec_cmd_with_quote.format(cmd)
    LOGGER.info(f"Exec_cmd_with_informix_env_old: {cmd}.")
    return_code, out, err = execute_cmd_oversize_return_value(cmd)
    return return_code, out, err


def get_local_database_version(env_dict, db_user=InformixInfo.INFORMIX_USER):
    ret, out, err = exec_cmd_with_informix_env(env_dict, InformixBaseCommand.QUERY_STATUS, db_user)
    if ret != CMDResult.SUCCESS:
        return ''
    res_list = out.split("\n")
    if len(res_list) <= 1:
        return ""
    version_line = res_list[1]
    if not version_line.startswith(InformixInfo.INFORMIX_VERSION_PREFIX):
        return ''
    if version_line.count("--") != 3:
        return ''
    version_list = version_line.replace(InformixInfo.INFORMIX_VERSION_PREFIX, "").strip().split("--")
    version = version_list[0].strip()
    return version


def check_instance_close(env_dict, db_user=InformixInfo.INFORMIX_USER):
    ret, out, err = exec_cmd_with_informix_env(env_dict, InformixBaseCommand.QUERY_STATUS, db_user)
    if ret == CMDResult.SUCCESS:
        return False
    return True


def set_xbsa_path(env_dict, db_user=InformixInfo.INFORMIX_USER):
    """
    设置onbar工具调用的xbsa库路径
    """
    ret, out, err = exec_cmd_with_informix_env(env_dict, InformixBaseCommand.SET_BAR_BSALIB_PATH_ENV, db_user)
    if ret != CMDResult.SUCCESS:
        return False
    return True


def remove_file_dir(path_):
    ret, realpath = check_path_in_white_list(path_)
    if not ret:
        raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
    if not os.path.exists(realpath):
        return True
    if os.path.isfile(realpath):
        try:
            su_exec_rm_cmd(realpath)
        except Exception as e_info:
            LOGGER.exception(e_info)
            return False
    else:
        try:
            shutil.rmtree(realpath)
        except Exception as e_info:
            LOGGER.exception(e_info)
            return False
    return True


def remove_file(path):
    try:
        os.remove(path)
    except Exception as err_info:
        LOGGER.exception(err_info)
        return False
    return True


def get_informix_dir(db_user=InformixInfo.INFORMIX_USER):
    """
    获取informix安装路径
    """
    targert_env = "GBASEDBTDIR" if db_user == GbaseInfo.GBASE_USER else "INFORMIXDIR"
    exec_cmd = GbaseCommand.EXEC_WITH_GBASEDBT \
        if db_user == GbaseInfo.GBASE_USER else InformixBaseCommand.EXEC_WITH_INFORMIX
    cmd = cmd_format(InformixBaseCommand.ECHO_TARGET_ENV, targert_env)
    cmd = cmd_format(exec_cmd, cmd)
    ret, out, err = execute_cmd(cmd)
    if ret != CMDResult.SUCCESS:
        return ''
    path = out.strip()
    if not os.path.exists(path):
        return ''
    if os.path.islink(path):
        return ''
    if not check_path_user(path, db_user, db_user):
        return ''
    return path


def check_path_user(path_, user, group):
    """
    校验路径所属用户，属组
    """
    try:
        stat_info = os.stat(path_)
    except Exception as exception:
        LOGGER.error(exception)
        return False
    path_uid = stat_info.st_uid
    path_gid = stat_info.st_gid
    path_user = pwd.getpwuid(path_uid)[0]
    path_group = grp.getgrgid(path_gid)[0]
    if not all([user == path_user, path_group == group]):
        return False
    return True


def kill_log_backup_progress():
    informix_dir = get_informix_dir()
    if not informix_dir:
        LOGGER.error(f"Get informix dir fail")
        return False
    onbar_d_path = os.path.join(informix_dir, "bin", "onbar_d")
    cmd = 'ps -ef'
    result = cmd_grep(f"{onbar_d_path} -b -l -C", cmd)
    LOGGER.info(f"Get log backup progress result:%s", result)
    if not result:
        return True
    first_row = result[0]
    row_array = first_row.split()
    if len(row_array) < 2:
        LOGGER.error(f"The command result is invalid: %s", row_array)
        return False
    cmd = cmd_format("su - {} -c \"kill -9 {}\"", InformixInfo.INFORMIX_USER, row_array[1])
    LOGGER.info(f"Get log backup progress cmd:%s", cmd)
    ret, out, err = execute_cmd(cmd)
    if ret != CMDResult.SUCCESS:
        LOGGER.error(f"Failed to exec command: %s, error: %s", cmd, err)
        return False
    return True


def set_log_cachinfo(instance_name, task_id, task_type):
    cache_info = {
        "cacheRepoPath": f"{get_install_head_path()}/DataBackup/",
        "metaRepoPath": f"{get_install_head_path()}/DataBackup/",
        "copyId": '1',
        "taskId": task_id,
        "hostKey": instance_name
    }
    cache_file_path = os.path.join(InformixPath.AGENT_STEMP_PATH,
                                   f"informix_{instance_name}_{instance_name}_{task_type}_log.txt")
    output_execution_result_ex(cache_file_path, cache_info)
    set_user(cache_file_path, 'rdadmin')
    return set_permisson(cache_file_path, PERMISSION_600)


def set_task_info(local_log_backup_path, instance_name):
    """
    生成备份路径信息供xbsa读取
    """
    task_info = {
        "repositories": [{
            "role": 0,  # 当前仓库的角色，0-master，1-slave
            "deviceSN": "xxxx",  # 设备ESN
            "filesystems": [{  # 数据仓的文件系统列表，不包含cache仓和元数据仓，因为cache仓和元数据仓通过dws_cacheInfo.txt指定了
                "id": "xxx",
                "name": "xxx",
                "sharePath": "xxx",
                "mountPath": []  # 框架挂载的文件系统本地挂载点路径，不区分协议类型（NFS/dataturbo）
            }]
        }],
        "archiveFileServers": [{
            "ip": "xxx",
            "port": 30066,
            "sslEnabled": ""
        }],
        "taskType": 0,  # 任务类型，0-备份，1-恢复，2-删除
        "copyType": 1  # 副本类型，与agent thrift接口定义的enum CopyDataType类型一致
    }
    task_file_path = f"{get_install_head_path()}/DataBackup/tmp/1/taskInfo_{instance_name}.txt"
    repositories = task_info.get("repositories", [])
    if not repositories:
        return False
    filesystems = repositories[0].get("filesystems", [])
    if not filesystems:
        return False
    filesystems[0]["mountPath"] = [local_log_backup_path]
    output_execution_result_ex(task_file_path, task_info)
    ret = set_permisson(f"{get_install_head_path()}/DataBackup/tmp/", PERMISSION_700)
    if not ret:
        return False
    set_user(f"{get_install_head_path()}/DataBackup/tmp/", 'rdadmin')
    ret = set_permisson(f"{get_install_head_path()}/DataBackup/tmp/1/", PERMISSION_700)
    if not ret:
        return False
    set_user(f"{get_install_head_path()}/DataBackup/tmp/1/", 'rdadmin')
    ret = set_permisson(task_file_path, PERMISSION_600)
    if not ret:
        return False
    set_user(task_file_path, 'rdadmin')
    return True


def pre_log_backup_path(local_logbackup_path, instance_name_curr, task_id, task_type,
                        db_user=InformixInfo.INFORMIX_USER):
    """准备日志副本路径和xbsa所需参数文件"""
    if not local_logbackup_path:
        return True
    if not mkdir_set_permissions(local_logbackup_path, db_user):
        LOGGER.error("Fail to create local log backup path")
        return False
    meta_path = PathInfo.XBSA_META_PATH
    if not mkdir_set_permissions(meta_path, "rdadmin"):
        LOGGER.error("Fail to meta path.")
        return False
    if not mkdir_set_permissions(PathInfo.XBSA_TMP_PATH, "rdadmin"):
        LOGGER.error("Fail to create XBSA_TMP_PATH path.")
        return False
    db_dir_path = os.path.join(meta_path, 'objectmeta', instance_name_curr)
    if not mkdir_set_permissions(db_dir_path, "rdadmin"):
        LOGGER.error("Fail to create db_file_path path.")
        return False
    set_log_cachinfo(instance_name_curr, task_id, task_type)
    return set_task_info(local_logbackup_path, instance_name_curr)


def set_xbsa_josn():
    """设置应用类型供xbsa server读取"""
    LOGGER.info("Start configure the application type.")
    xbsa_json_path = os.path.join(FileConstant.AGENT_CONF_PATH, "xbsa.json")
    app_type_info = {"app_type": "informix"}
    if not os.path.exists(xbsa_json_path):
        output_execution_result_ex(xbsa_json_path, app_type_info)
        if not change_path_permission(xbsa_json_path, "rdadmin"):
            return False
        return True
    xbsa_dic = read_tmp_json_file(xbsa_json_path)
    if xbsa_dic.get('app_type') == "informix":
        if not change_path_permission(xbsa_json_path, "rdadmin"):
            return False
        return True
    output_execution_result_ex(xbsa_json_path, app_type_info)
    if not change_path_permission(xbsa_json_path, "rdadmin"):
        return False
    return True


def get_informix_version():
    """获取informix版本信息"""
    cmd = cmd_format(InformixBaseCommand.EXEC_WITH_INFORMIX, InformixBaseCommand.INFORMIX_VERSION)
    ret, out, err = execute_cmd(cmd)
    if ret != CMDResult.SUCCESS:
        LOGGER.error(f"ret: {ret}, outL: {out}, err: {err}.")
        return ""
    result = out.split("\n")
    LOGGER.info(f"out: {out}, result:  {result}.")
    version_line = ""
    # Build Version:	11.70.FC1TL/Build Version:		12.10.FC12TL
    for line in result:
        if "Build Version:" in line:
            version_line = line
            break
    if not version_line:
        LOGGER.error(f"The version information is not found, result: {result}.")
        return ""

    version_line_list = version_line.split()
    version_result = version_line_list[-1].split(".")
    if len(version_result) != 3:
        LOGGER.error(f"TThe version information format is incorrect, version_line_list: "
                     f"{version_line_list}, version_result: {version_result}.")
        return ""
    LOGGER.info(f"The current Informix version is {version_result[0]}.")
    return version_result[0]


def set_informix_conf(db_user=InformixInfo.INFORMIX_USER):
    """设置xbsa 客户端配置文件"""
    LOGGER.info("Start configuring the application version.")
    # 0. 环境若为Gbase8s环境则不需要设置
    if db_user == GbaseInfo.GBASE_USER:
        return True

    # 1. 获取informix版本信息
    informix_version = get_informix_version()
    if not informix_version:
        return False

    # aix下informix12与informix11使用同一份协议
    if platform.system().lower() == "aix":
        informix_version = "12"

    # 2. 配置参数文件
    informix_conf_path = os.path.join(PathConstant.XBSA_PATH, "conf", "informix.conf")
    if not os.path.exists(informix_conf_path):
        # 参数文件不存在，直接写文件
        informix_info = {"version": informix_version}
        output_execution_result_ex(informix_conf_path, informix_info)
        if not change_path_permission(informix_conf_path, db_user):
            return False
        return True

    ret, real_path = check_path_in_white_list(informix_conf_path)
    if not ret:
        raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
    # 3.参数文件存在，判断版本号是否一致，不一致则更新参数文件
    informix_dic = read_tmp_json_file(informix_conf_path)
    if informix_dic.get("version") == informix_version:
        return True
    informix_dic["version"] = informix_version
    output_execution_result_ex(informix_conf_path, informix_dic)
    if not change_path_permission(informix_conf_path, "rdadmin"):
        return False
    LOGGER.info(f"Version updated successfully")
    return True


def check_mount(path, resource_id):
    """
    检查路径是否被指定资源id的文件系统挂载,如果不是则解除挂载
    """
    result = cmd_grep(path, "mount")
    if not result:
        LOGGER.error(f'Mount point not exists')
        return False
    if resource_id in result[0]:
        return True
    return False


def check_mount_path(path):
    """
    检查路径是否被指定资源id的文件系统挂载,如果不是则解除挂载
    """
    result = cmd_grep(path, "mount")
    if not result:
        LOGGER.error(f'Mount point not exists, path : %s', path)
        return False
    return True


def timestamp(time_):
    s_t = time.strptime(time_, "%m/%d/%Y %H:%M:%S")
    mkt = int(time.mktime(s_t))
    return mkt


def get_log_time_info(ixbar_path, log_id):
    with open(ixbar_path, 'r') as file:
        lines = file.readlines()
        for line in lines:
            line_array = line.split()
            if len(line_array) < 20:
                LOGGER.error('The data of ixbar is invalid:%s', line)
                continue
            if line_array[1] == str(log_id):
                start_time = line_array[19]
                end_time = line_array[18]
                start_time = end_time if start_time == '0' else start_time
                return True, int(start_time), int(end_time)
    LOGGER.error(f"Failed to find log records, log_id: {log_id}.")
    return False, '', ''


def get_last_root_dbs_time(ixbar_path):
    is_get_success = False
    last_root_dbs_time = ''
    last_root_dbs_time_list = []
    with open(ixbar_path, 'r') as file:
        lines = file.readlines()
        for line in lines:
            line_array = line.split()
            if len(line_array) < 20:
                LOGGER.error('The data of ixbar is invalid:%s', line)
                continue
            if line_array[2] != 'L':
                last_root_dbs_time_list.append(int(line_array[18]))
    if last_root_dbs_time_list:
        last_root_dbs_time = max(last_root_dbs_time_list)
        is_get_success = True
        LOGGER.info(f"Full backup time is: {last_root_dbs_time}.")
    return is_get_success, last_root_dbs_time


def umount(path_, mount_type=''):
    if mount_type == MountType.FUSE:
        umount_cmd = f"{PathConstant.FILE_CLIENT_PATH} --remove --mount_point={path_}"
        return_code, std_out, std_err = execute_cmd(umount_cmd)
        if return_code == CMDResult.SUCCESS:
            LOGGER.info(f"Fuse umount {path_} succ.")
            return True
        LOGGER.error(f"Fuse umount {path_} failed.")
        return False
    result = cmd_grep(path_, "mount")
    if not result:
        LOGGER.debug(f'Mount point not exists')
        return True
    if check_command_injection_ex(path_):
        LOGGER.debug(f'Unmount path is invalid.')
        return False
    return_code, out_info, _ = exec_umount_cmd(path_, "-f")
    if return_code == CMDResult.SUCCESS:
        LOGGER.info(f'Umount %s success.', path_)
        return True
    LOGGER.warning(f'Umount %s failed.', path_)
    return False


def backup_mount_bind(path_, new_path, mount_type='', osad_params=None):
    """
    日志挂载目录mount bind
    :return: bind结果,'新路径'
    """
    query_cmd = "uname -s"
    _, std_out, _ = execute_cmd(query_cmd)
    if mount_type == MountType.FUSE:
        osad_ip_list, osad_auth_port, osad_server_port, job_id = osad_params
        mount_cmd = f"{PathConstant.FILE_CLIENT_PATH} --add --mount_point={new_path} " \
                    f"--source_id={job_id} --osad_ip_list={osad_ip_list} " \
                    f"--osad_auth_port={osad_auth_port}  --osad_server_port={osad_server_port}"
        return_code, std_out, std_err = execute_cmd(mount_cmd)
    elif "AIX" in std_out:
        return_code, std_out, std_err = exec_mount_cmd_with_aix(path_, new_path)
    else:
        return_code, std_out, std_err = exec_mount_cmd(path_, new_path)
    if return_code == CMDResult.SUCCESS:
        LOGGER.info("Mount succ.")
        return True, new_path
    LOGGER.error(f"Mount fail. return_code: {return_code}, std_out: {std_out}, std_err: {std_err}")
    return False, ''


def umount_bind(path_, mount_type=''):
    query_cmd = "uname -s"
    _, std_out, _ = execute_cmd(query_cmd)
    if mount_type == MountType.FUSE:
        umount_cmd = f"{PathConstant.FILE_CLIENT_PATH} --remove --mount_point={path_}"
        return_code, std_out, std_err = execute_cmd(umount_cmd)
    elif "AIX" in std_out:
        return_code, std_out, std_err = exec_umount_cmd(path_, "-f")
    else:
        return_code, std_out, std_err = exec_umount_cmd(path_, "-f")
    if return_code == CMDResult.SUCCESS:
        LOGGER.info("Umount succ.")
        return True
    LOGGER.error(f"UMount fail. return_code: {return_code}, std_out: {std_out}, std_err: {std_err}")
    return False


def mount_filesystem(ip, path_, new_path, mount_type='', osad_params=None):
    """
    日志挂载目录mount bind
    :return: bind结果,'新路径'
    """
    cmd = cmd_format("mount {}:{} {}", ip, path_, new_path)
    if mount_type == MountType.FUSE:
        osad_ip_list, osad_auth_port, osad_server_port, job_id = osad_params
        cmd = f"{PathConstant.FILE_CLIENT_PATH} --add --mount_point={new_path} " \
              f"--source_id={job_id} --osad_ip_list={osad_ip_list} " \
              f"--osad_auth_port={osad_auth_port}  --osad_server_port={osad_server_port}"
    LOGGER.info(f"Mount cmd: {cmd}.")
    return_code, out, err = execute_cmd(cmd)
    if return_code == CMDResult.SUCCESS:
        LOGGER.info("Mount succ.")
        return True
    LOGGER.error("Mount fail.err:%s, out:%s", err, out)
    return False


def mount_by_script(job_id, repository, mount_path, extend_info):
    """
    日志挂载目录
    :return: 挂载结果 True/False
    """
    json_str = {
        "user": InformixInfo.INFORMIX_USER,
        "fileMode": "0755"
    }
    repository["path"] = [mount_path]
    param_json = {
        "repository": [repository],
        "permission": json_str,
        "extendInfo": extend_info,
        ParamKeyConst.JOB_ID: job_id
    }

    try:
        ret = invoke_rpc_tool_interface(f"mount_{job_id}", RpcToolInterface.MOUNT_REPO.value, param_json)
    except Exception as err_info:
        LOGGER.error(f"Get last copy info failed.{err_info}")
        return False
    ret_code = ret.get("code")
    if ret_code != 0:
        LOGGER.error(f"Invoke rpc_tool interface failed, result code: {ret_code}.")
        return False
    return True


def get_std_in_variable(str_env_variable):
    env_variable = ''
    input_dict = json.loads(SysData.SYS_STDIN)
    if input_dict.get(str_env_variable):
        env_variable = input_dict.get(str_env_variable)
    return env_variable


def get_remote_host_ips(repositories):
    ips = []
    for repository in repositories:
        for item in repository.get('remoteHost', []):
            ip = item.get('ip', '')
            if ip not in ips:
                ips.append(ip)
    return ",".join(ips)


def set_status(instance_name, instance_type="primary", db_user=InformixInfo.INFORMIX_USER):
    """
    设置节点状态
    """
    cmd = cmd_format(InformixBaseCommand.SET_PAIMARY, instance_name)
    if instance_type == "secondary":
        cmd = cmd_format(InformixBaseCommand.SET_SECONDARY, instance_name)
    exec_with_user = InformixBaseCommand.EXEC_WITH_INFORMIX if db_user == InformixInfo.INFORMIX_USER \
        else GbaseCommand.EXEC_WITH_GBASEDBT
    cmd = cmd_format(exec_with_user, cmd)
    ret, out, err = execute_cmd(cmd)
    if ret != CMDResult.SUCCESS:
        LOGGER.error(f"Failed to set the database status.out:%s, err:%s", out, err)
        return False
    return True


def change_dir_permission(copy_data_model):
    # 反向复制场景恢复需要创建临时文件，需要修改克隆文件副本目录权限，不影响原始副本目录权限
    if "clone" in copy_data_model.src_path:
        cmd = f'chmod -R 777 {copy_data_model.src_path} {copy_data_model.des_path}'
        ret, out, err = execute_cmd(cmd)
        if ret != CMDResult.SUCCESS:
            LOGGER.error(f"{cmd} error.")
            return False
        LOGGER.info(f"{cmd} success.")
    return True


def create_bsaobjtable_sql():
    return "CREATE TABLE [BsaObjTable] ([copyId] VARCHAR(100) NOT NULL," \
           "[objectSpaceName] VARCHAR(1024),[objectName] VARCHAR(1024) NOT NULL," \
           "[bsaObjectOwner] VARCHAR(64)," \
           "[appObjectOwner] VARCHAR(64),[copyType] INTEGER(8)," \
           "[estimatedSize] VARCHAR(100) NOT NULL," \
           "[resourceType] VARCHAR(32),[objectType] INTEGER(8),[objectStatus] INTEGER(8)," \
           "[objectDescription] VARCHAR(100),[objectInfo] VARCHAR(256),[timestamp] VARCHAR(64)," \
           "[restoreOrder] VARCHAR(100),[storePath] VARCHAR(1280) NOT NULL," \
           "[filesystemName] VARCHAR(256) NOT NULL,[filesystemId] VARCHAR(128) NOT NULL," \
           "[filesystemDeviceId] VARCHAR(256) NOT NULL,[rsv1] VARCHAR(256),[rsv2] VARCHAR(256));"


def aggregate_single_copy_object_data(copy_data_model: CopyDataModel, is_restore_task=True):
    """
    功能描述：将日志备份的obj合并到数据备份的obj对象中
    cache_path：合成的文件要存放的路径
    object_data_path：被合成的对象数据文件存放路径
    """
    concrete_object_db = "backupkey.db"
    try:
        object_conn = sqlite3.connect(copy_data_model.dst_obj_path)
    except Exception as ex:
        LOGGER.error(f"Connect sqlite %s failed for %s.main task:%s", concrete_object_db, ex, copy_data_model.job_id)
        return False
    object_cur = object_conn.cursor()
    if not object_conn or not object_cur:
        LOGGER.error(f"Connect sqlite %s failed.main task:%s", concrete_object_db, copy_data_model.job_id)
        return False
    object_tables = object_cur.execute("select name from sqlite_master where type='table'").fetchall()
    if not object_tables:
        object_cur.execute(create_bsaobjtable_sql())
    object_tables = object_cur.execute("select name from sqlite_master where type='table'").fetchall()
    if not object_tables or len(object_tables) == 0:
        LOGGER.error("Create dws table failed. main task:%s", copy_data_model.job_id)
        return False
    src_obj = sqlite3.connect(copy_data_model.src_obj_path)
    temp_object_cur = src_obj.cursor()
    obj_name = object_cur.execute("select objectName from BsaObjTable").fetchall()
    for line in temp_object_cur.execute("select * from BsaObjTable").fetchall():
        str_line = str(line).replace("None", "''")
        is_path_valid = copy_data_model.src_path and copy_data_model.des_path
        if is_path_valid and is_in_list(obj_name, line[2]):
            if "ixbar" in line[2] or "oncfg" in line[2] or "onconfig" in line[2]:
                cmd = cmd_format("UPDATE BsaObjTable SET storePath = \'{}\' where objectName = \'{}\'",
                                 str(line[14]), str(line[2]))
                object_cur.execute(cmd)
                object_conn.commit()
                copy_file_to_dest_dir(copy_data_model.src_path, copy_data_model.des_path,
                                      str(line[14]), copy_data_model.db_user)
                continue
            continue
        if is_restore_task:
            real_src_path = f"{copy_data_model.src_path}/{str(line[14])}"
            real_src_path_ = f"{copy_data_model.src_path}/{str(line[14]).split('/')[-1].strip()}"
            if not os.path.exists(real_src_path) and not os.path.exists(real_src_path_):
                continue
        object_cur.execute(f"insert into BsaObjTable values {str_line}")
        if is_path_valid:
            copy_file_to_dest_dir(copy_data_model.src_path, copy_data_model.des_path,
                                  str(line[14]), copy_data_model.db_user)
        object_conn.commit()
    object_close(object_conn, object_cur, src_obj, temp_object_cur)
    time.sleep(3)
    return True


def object_close(object_conn, object_cur, src_obj, temp_object_cur):
    temp_object_cur.close()
    src_obj.close()
    object_cur.close()
    object_conn.close()


def is_in_list(ret_list, node):
    for item in ret_list:
        if node in item:
            return True
    return False


def update_db_new_instance_name_and_id(db_path, instance_name, instance_id, old_name, old_id):
    LOGGER.info(f"Start update db by instance name and instance id.")
    try:
        db_connect_handle = sqlite3.connect(db_path)
    except Exception as ex:
        LOGGER.error(f"Connect db file, db: {db_path}, Exception: {ex}")
        return False
    db_cursor_handle = db_connect_handle.cursor()
    if not db_connect_handle or not db_cursor_handle:
        LOGGER.error(f"Get db Handle failed, db: {db_path}")
        return False
    for line in db_cursor_handle.execute("select * from BsaObjTable").fetchall():
        try:
            copy_id, new_line = convert_data_by_name_id(line, instance_name, instance_id, old_name, old_id)
        except Exception as ex:
            LOGGER.error(f"Convert Error: {ex}")
            return False
        delete_cmd = cmd_format("DELETE from BsaObjTable where copyId = \'{}\'", copy_id)
        db_cursor_handle.execute(delete_cmd)
        insert_cmd = f"insert into BsaObjTable values {new_line}"
        db_cursor_handle.execute(insert_cmd)
        db_connect_handle.commit()
    db_cursor_handle.close()
    db_connect_handle.close()
    LOGGER.info(f"End update db by instance name and instance id.")
    return True


def convert_data_by_name_id(query_str, instance_name, instance_id, old_name, old_id):
    LOGGER.info(f"Start to update the data instance name, instance_name: {instance_name}, "
                f"old_name: {old_name}, old_id: {old_id}.")
    copy_id = query_str[0]
    query_strs = str(query_str).replace("None", "''")
    query_list = query_strs.split(',')
    if 'L' in query_str[7]:
        tmp_list = query_list[2].split('/')
        query_list[2] = tmp_list[0] + f'/{tmp_list[1]}/{instance_id}/' + tmp_list[-1]
        LOGGER.info(f"Replace: {query_list[2]}")
    ret = ""
    for item in query_list:
        ret += item + ","
    return copy_id, ret[0:-1]


def copy_file_to_dest_dir(src_path, dest_path_parent, current_path, db_user=InformixInfo.INFORMIX_USER):
    parent_path = os.path.dirname(current_path)
    dest = f"{dest_path_parent}/{parent_path}"
    if not os.path.exists(dest):
        create_cmd = cmd_format(f'su - {db_user} -c "mkdir -p {{}}"', dest)
        ret, out, err = execute_cmd(create_cmd)
        if ret != CMDResult.SUCCESS:
            LOGGER.error(f"Dir Create Failed. ret: {ret}, out: {out}, err: {err}")
            return False
    is_exist_rootdbs_or_xiba = "rootdbs" in current_path or "ixbar" in current_path
    is_exist_oncfg_or_onconfig = "oncfg" in current_path or "onconfig" in current_path
    if is_exist_rootdbs_or_xiba or is_exist_oncfg_or_onconfig:
        real_path = f"{src_path}/{current_path}"
    else:
        real_path = f"{src_path}/{current_path.split('/')[-1].strip()}"
    if not os.path.exists(real_path):
        real_path = f"{src_path}/{current_path}"
    cp_cmd = f"cp -f {real_path} {dest}"
    LOGGER.info(f"Copy_file_to_dest_dir cmd: {cp_cmd}.")
    ret, out, err = execute_cmd(cp_cmd)
    if ret != CMDResult.SUCCESS:
        LOGGER.error(f"Cp File Failed. ret: {ret}, out: {out}, err: {err}")
        return False
    if current_path.startswith('/'):
        ret_path = f"{dest_path_parent}{current_path}"
    else:
        ret_path = f"{dest_path_parent}/{current_path}"
    set_user(ret_path, db_user)
    return True


def open_grep(key, path: str, ignore_case=False):
    result = []
    path = path.strip('"').strip("'")
    if not os.path.exists(path):
        return result
    if os.path.islink(path):
        return result
    with open(path, 'r') as f_obj:
        for line in f_obj:
            line = line.strip('\n')
            tmp_line = line
            tmp_key = key
            if ignore_case:
                tmp_line = line.lower()
                tmp_key = key.lower()
            if tmp_key in tmp_line:
                result.append(line)
    return result


def cmd_grep(key, cmd, ignore_case=False):
    LOGGER.info(f"Enter cmd_grep: {cmd}")
    result = []
    ret, out, err = execute_cmd(cmd)
    if ret != CMDResult.SUCCESS:
        return result
    out_list = out.split('\n')
    for line in out_list:
        tmp_line = line
        tmp_key = key
        if ignore_case:
            tmp_line = line.lower()
            tmp_key = key.lower()
        if tmp_key in tmp_line:
            result.append(line)
    return result


def read_result_file(file_name):
    """
    从结果文件读取数据
    :param file_name: 结果文件路径
    :return: 读取结果
    """
    with open(file_name, "r", encoding='UTF-8') as file_content:
        result = file_content.read()
        return result


def read_tmp_json_file(file_path):
    """
    读取临时json文件
    :param file_path: 文件路径
    :return: 文件内容
    """
    if not os.path.isfile(file_path):
        return {}
    with open(file_path, "r", encoding='UTF-8') as file_content:
        return json.load(file_content)


def execute_cmd(cmd, locale_encoding=False, timeout=None):
    """
    执行不包含管道符的命令
    :param cmd: 命令
    :param locale_encoding: 是否使用本地编码
    :param timeout: 执行命令超时
    :return: (字符串格式, 标准输出, 标准错误)
    """
    encoding = "utf-8"
    if locale_encoding:
        encoding = locale.getdefaultlocale()[1]
    process = subprocess.Popen(shlex.split(cmd), stdin=None, stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE, encoding=encoding)
    std_out, std_err = process.communicate(timeout=timeout)
    return str(process.returncode), str(std_out), str(std_err)


def output_result_file(pid, result_info):
    if not pid or not result_info:
        raise Exception("Param pid or result info is none")
    output_file_name = cmd_format("{}{}", "result", pid)
    output_file_path = os.path.join(ParamConstant.RESULT_PATH, output_file_name)
    output_execution_result(output_file_path, result_info)
    if not os.path.exists(output_file_path):
        raise Exception(f"Result file: {output_file_path} can not create")


def output_execution_result(file_path, payload):
    json_str = json.dumps(payload)
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
    modes = stat.S_IWUSR | stat.S_IRUSR | stat.S_IXUSR
    with os.fdopen(os.open(file_path, flags, modes), 'w') as f:
        f.write(json_str)


def output_execution_result_ex(file_path, payload):
    if os.path.exists(file_path):
        path_type = check_file_or_dir(file_path)
        if path_type != EnumPathType.FILE_TYPE:
            LOGGER.error(f"Check des path not file type: {path_type}")
            raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
    # 先将文件清空再写
    json_str = json.dumps(payload)
    flags = os.O_WRONLY | os.O_CREAT
    modes = stat.S_IWUSR | stat.S_IRUSR | stat.S_IXUSR
    with os.fdopen(os.open(file_path, flags, modes), 'w') as f:
        f.truncate(0)
        f.write(json_str)


def clean_dir(dir_path):
    ret, _ = check_path_in_white_list(dir_path)
    if not ret:
        raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
    for path in os.listdir(dir_path):
        new_path = os.path.join(dir_path, path)
        if os.path.isfile(new_path):
            su_exec_rm_cmd(new_path)
        elif os.path.isdir(new_path):
            shutil.rmtree(new_path)
    return True


def exter_attack(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper


def check_path_legal(path, parent_dir):
    """
    将路径转为绝对路径，并校验起是否在指定父目录下,防止目录逃逸
    :path : 需要检验的目录
    :parent_dir: 父目录
    :return: bool
    """
    abs_path = os.path.realpath(path)
    abs_parent_dir = os.path.realpath(parent_dir)
    if abs_path.startswith(abs_parent_dir):
        return True
    return False


def execute_cmd_oversize_return_value(cmd):
    """
    执行shell命令，不支持管道和重定向, 针对返回值超大的情况使用此命令
    :param cmd: shell命令
    :return:执行返回码，正确信息，错误信息
    """
    exitcode = 0
    try:
        data = subprocess.check_output(shlex.split(cmd), encoding="utf-8", shell=False, text=True,
                                       stderr=subprocess.STDOUT)
    except subprocess.CalledProcessError as ex:
        data = ex.output
        exitcode = ex.returncode
    except Exception as e_info:
        LOGGER.exception(e_info)
        # 存在utf-8 解析不了的返回信息。
        data = "cmd error"
        exitcode = 127
    if data[-1:] == '\n':
        data = data[:-1]
    return str(exitcode), data, data


def change_path_permission(des_path: str, user_name: str = None, mode=None):
    """
    修改路径权限包括文件和文件夹，不存在或者符号链接报错,
    :param mode:
    :param des_path:
    :param user_name:
    :return:
    """
    ret, _ = check_path_in_white_list(des_path)
    if not ret:
        raise ErrCodeException(ErrorCode.ERROR_PARAM.value)
    path_type = check_file_or_dir(des_path)
    if path_type in (EnumPathType.INVALID_TYPE, EnumPathType.LINK_TYPE):
        return False
    if user_name:
        user_group, user_info = get_user_info(user_name)
        if not user_group:
            return False
        stat_info = os.stat(des_path)
        if stat_info.st_uid != user_info.pw_uid or stat_info.st_gid != user_info.pw_gid:
            try:
                os.lchown(des_path, user_info.pw_uid, user_info.pw_gid)
            except Exception as e_info:
                LOGGER.exception(e_info)
                return False
    if mode:
        try:
            os.chmod(des_path, mode)
        except Exception as e_info:
            LOGGER.exception(e_info)
            return False
    return True


def check_file_or_dir(path):
    """
    检测路径类型，文件 文件夹 符号链接
    :param path:
    :return:
    """
    try:
        status = os.lstat(path).st_mode
    except Exception as e_info:
        LOGGER.exception(e_info)
        return EnumPathType.INVALID_TYPE
    if S_ISLNK(status):
        return EnumPathType.LINK_TYPE
    elif S_ISDIR(status):
        return EnumPathType.DIR_TYPE
    elif S_ISREG(status):
        return EnumPathType.FILE_TYPE
    return EnumPathType.INVALID_TYPE


def get_user_info(user_name):
    """
    获取用户信息，不存在报错
    :param user_name:
    :return: group_name user_info
    """
    try:
        user_info = pwd.getpwnam(str(user_name))
    except Exception as e_info:
        LOGGER.exception(e_info)
        return "", ""
    group_id = user_info.pw_gid
    user_group = grp.getgrgid(group_id).gr_name
    return user_group, user_info


def get_db_file_size(root_dbs_file_path):
    file_size = 0
    if not os.path.exists(root_dbs_file_path):
        return file_size
    return os.path.getsize(root_dbs_file_path)


def write_onbar_process_info(log_path, resource_id):
    # 记录onbar进程号信息
    pid = get_onbar_pid()
    if not pid:
        LOGGER.error(f"Get onbar pid failed.")
        return False
    onbar_info_file = os.path.join(log_path, 'onbar_process_info')
    flags = os.O_RDWR | os.O_CREAT
    modes = stat.S_IWUSR | stat.S_IRUSR
    with os.fdopen(os.open(onbar_info_file, flags, modes), 'w') as f:
        f.write(f'{resource_id} {pid}')
    return True


def get_onbar_pid():
    informix_dir = get_informix_dir()
    if not informix_dir:
        LOGGER.error(f"Get informix dir fail.")
        return ""
    onbar_d_path = os.path.join(informix_dir, "bin", "onbar_d")
    cmd = 'ps -ef'
    result = cmd_grep(f"{onbar_d_path} -b -l -C", cmd)
    LOGGER.info(f"Get onbar progress result:%s", result)
    if not result:
        return ""
    first_row = result[0]
    row_array = first_row.split()
    if len(row_array) < 2:
        LOGGER.error(f"Get onbar pid failed.")
        return ""
    return row_array[1]


def read_onbar_process_file(onbar_info_file):
    file = open(onbar_info_file)
    content = file.read()
    file.close()
    return content


def read_grep(file_path: str, key):
    if not os.path.exists(file_path):
        LOGGER.error(f"The file {file_path} does not exist.")
        return []
    file_object = open(file_path, 'r')
    try:
        all_content = file_object.readlines()
    except Exception as e_info:
        LOGGER.exception(e_info)
        return []
    finally:
        file_object.close()
    result = []
    for line in all_content:
        if key in line:
            result.append(line)
    return result


def check_xbsa_config(onconfig_path: str):
    LOGGER.info(f"Start to check the xbsa configuration, onconfig: {onconfig_path}.")
    result = read_grep(onconfig_path, InformixInfo.BAR_BSALIB_PATH)
    if not result:
        LOGGER.error("The configuration item BAR_BSALIB_PATH does not exist.")
        return False
    LOGGER.info(f"The content of the parameter file is {result}.")
    for line in result:
        if InformixInfo.BAR_BSALIB_PATH in line and InformixPath.LIB_XBSA_PATH in line:
            return True
    LOGGER.error(f"The xbsa library is not configured, result: {result}.")
    return False


def read_file_by_line(file_path: str):
    ret, _ = check_path_in_white_list(file_path)
    if not ret:
        LOGGER.error(f"Invalid path, {file_path}.")
        return []
    file_src_object = open(file_path, 'r')
    try:
        all_content = file_src_object.readlines()
    except Exception as e_info:
        LOGGER.exception(e_info)
        return []
    finally:
        file_src_object.close()
    return all_content


def write_file_by_line(file_path: str, all_content: list):
    ret, _ = check_path_in_white_list(file_path)
    if not ret:
        LOGGER.error(f"Invalid path, {file_path}.")
        return False
    flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
    modes = stat.S_IWUSR | stat.S_IRUSR | stat.S_IXUSR
    with os.fdopen(os.open(file_path, flags, modes), 'w') as f:
        f.writelines(all_content)
    return True


def check_log_backup_path(user, log_backup_path):
    """
    检查路径是否存在，并检查呢用户是否有读写权限
    :param user:
    :param log_backup_path:
    :return:
    """
    if check_command_injection(log_backup_path):
        LOGGER.error("Failed to verify the parameter(log_backup_path) validity.")
        return ErrorCode.ERROR_PARAM.value
    if not os.path.exists(log_backup_path):
        LOGGER.error("The log backup path does not exist.")
        return ErrorCode.ERR_LOG_BACKUP_PATH.value
    if is_readable(log_backup_path, user) and is_writable(log_backup_path, user):
        LOGGER.info(f"{user} has read and write access to {log_backup_path}")
        return ErrorCode.MY_SUCCESS_CODE
    else:
        LOGGER.error(f"{user} does not have read and write access to {log_backup_path}")
        return ErrorCode.ERR_LOG_BACKUP_PATH.value


def is_readable(path, user):
    user_info = pwd.getpwnam(user)
    uid = user_info.pw_uid
    gid = user_info.pw_gid
    s = os.stat(path)
    mode = s[stat.ST_MODE]
    return (
            ((s[stat.ST_UID] == uid) and (mode & stat.S_IRUSR > 0)) or
            ((s[stat.ST_GID] == gid) and (mode & stat.S_IRGRP > 0)) or
            (mode & stat.S_IROTH > 0)
    )


def is_writable(path, user):
    user_info = pwd.getpwnam(user)
    uid = user_info.pw_uid
    gid = user_info.pw_gid
    s = os.stat(path)
    mode = s[stat.ST_MODE]
    return (
            ((s[stat.ST_UID] == uid) and (mode & stat.S_IWUSR > 0)) or
            ((s[stat.ST_GID] == gid) and (mode & stat.S_IWGRP > 0)) or
            (mode & stat.S_IWOTH > 0)
    )


def identify_env_user():
    LOGGER.info("First, test out if this is a informix environment")
    cmd = f"su {InformixInfo.INFORMIX_USER} -c 'echo 0'"
    ret, std_out, std_err = execute_cmd(cmd)
    if ret != CMDResult.SUCCESS.value:
        LOGGER.info("Test if this is a GBase8s envirionment")
        cmd = f"su {GbaseInfo.GBASE_USER} -c 'echo 0'"
        ret, std_out, std_err = execute_cmd(cmd)
        if ret != CMDResult.SUCCESS.value:
            LOGGER.error("This environment is neither a informix or a GBase8s environment")
            return ''
        LOGGER.info("This is GBase8s")
        return GbaseInfo.GBASE_USER
    LOGGER.info("This is informix")
    return InformixInfo.INFORMIX_USER
